package org.xteam.camllight;

import java.util.Stack;

public class MachineState {

	private Stack<Object> argumentStack = new Stack<Object>();
	private Stack<Object> returnStack = new Stack<Object>();
	private Object accu = Value.valLong(0);
	private Object env = Atom.get(0);
	private Block globals;
	private int cacheSize = 0;
	private TrapFrame tp = null;
	
	public MachineState(Block globals) {
		this.globals = globals;
	}

	public Object env() {
		return env;
	}

	public void setAccu(Object object) {
		this.accu = object;
	}

	public Object getAccu() {
		return accu;
	}

	public void setGlobal(int num, Object value) {
		this.globals.setField(num, value);
	}

	public void pushReturn(Object value) {
		returnStack.push(value);
		cacheSize++;
	}

	public Object access(int n) {
		if (cacheSize > n) {
			return returnStack.get(returnStack.size() - n - 1);
		}
		return Value.field(env, n - cacheSize);
	}

	public Object access0() {
		if (cacheSize > 0) {
			return returnStack.get(returnStack.size() - 1);
		}
		return Value.field(env, 0);
	}

	public void endlet(int n) {
		if (cacheSize >= n) {
			cacheSize -= n;
			popReturn(n);
		} else {
			n -= cacheSize;
			popReturn(cacheSize);
			Block block = (Block)env;
			int i = block.size();
			cacheSize = i - n;
			for (i = cacheSize; i > 0; i--) {
				returnStack.push(block.field(i));
			}
			env = Atom.get(0);
		}
	}
	
	public void popReturn(int n) {
		while (n-- > 0) {
			returnStack.pop();
		}
	}

	public void pushArgument(Object value) {
		argumentStack.push(value);
	}
	
	public Object popArgument() {
		return argumentStack.pop();
	}

	public Object getGlobal(int num) {
		if (globals.field(num) == null) {
			throw new RuntimeException("*burp* global is null");
		}
		return globals.field(num);
	}

	public void pushReturnFrame(CodePointer codePointer) {
		returnStack.push(new ReturnFrame(codePointer, env, cacheSize));
		cacheSize = 0;
	}
	
	public CodePointer popReturnFrame() {
		ReturnFrame frame = (ReturnFrame) popReturn();
		env = frame.env();
		cacheSize = frame.cacheSize();
		return frame.codePointer();
	}

	public CodePointer doApply() {
		Closure closure = (Closure) accu;
		env = closure.env();
		return closure.codePointer();
	}

	public Object peekArgument() {
		return argumentStack.peek();
	}

	public void pushArgIntoReturn() {
		pushReturn(popArgument());
	}

	public void heapifyEnv() {
		if (cacheSize > 0) {
			int envSize = Value.sizeOf(env);
			int newSize = envSize + cacheSize;
			Block tmp = new Block(0, newSize);
			int index = 0;
			for(; cacheSize > 0; cacheSize--) {
				tmp.setField(index++, popReturn());
			}
			for(int i = 0; i < envSize; ++i) {
				tmp.setField(index++, Value.get(env, i));
			}
			env = tmp;	
		}
	}

	public Object popReturn() {
		return returnStack.pop();
	}

	public void popCached() {
		popReturn(cacheSize);
		cacheSize = 0;
	}

	public void pushTrapFrame(CodePointer codePointer) {
		int offset = returnStack.size() - cacheSize;
		returnStack.push(tp = new TrapFrame(codePointer, env, cacheSize + 2, argumentStack.size(), tp));
		for (int i = 0; i < cacheSize; ++i) {
			returnStack.push(returnStack.get(offset + i));
	    }
	    pushArgument(Mark.INSTANCE);
	}

	public void popTrapFrame() {
		while (returnStack.peek() != tp) {
			returnStack.pop();
		}
		returnStack.pop();
		env = tp.env();
		cacheSize = tp.cacheSize() - 2;
		while (argumentStack.size() != tp.argumentStackSize()) {
			argumentStack.pop();
		}
		tp = tp.next();
	}
	
}
